﻿using System;
using JetBrains.Annotations;
using JetBrains.ReSharper.Psi;
using JetBrains.ReSharper.Psi.Xaml.Impl.Tree;
using JetBrains.ReSharper.Psi.Xaml.Tree;
using JetBrains.ReSharper.Psi.Xml.Tree;

namespace GammaJul.ReSharper.GammaXaml.Psi.Xaml {

	/// <summary>
	/// Contains extension methods related to PSI, specific to XAML.
	/// </summary>
	public static class XamlPsiExtensions {

		/// <summary>
		/// Gets a XAML property setter from a given XAML object.
		/// </summary>
		/// <param name="xamlObjectElement">The XAML object.</param>
		/// <param name="propertySetter">The property setter representing the property attribute to get.</param>
		/// <returns>A <see cref="IXmlToken"/> representing the value token of <paramref name="propertySetter"/>, or <c>null</c> if none could be found.</returns>
		[CanBeNull]
		public static IPropertySetter GetPropertySetter([CanBeNull] this IXamlObjectElement xamlObjectElement, [CanBeNull] ITypeMember propertySetter) {
			if (xamlObjectElement == null || propertySetter == null)
				return null;

			// search the attributes first (eg Grid.Row="0")
			var propertyAttribute = xamlObjectElement.GetAttribute(attr => {
				var propertyAttr = attr as IPropertyAttribute;
				if (propertyAttr == null)
					return false;
				ITypeMemberReference memberRef = propertyAttr.MemberReference;
				return memberRef != null && Equals(memberRef.Resolve().DeclaredElement, propertySetter);
			}) as IPropertyAttribute;

			if (propertyAttribute != null)
				return propertyAttribute;

			// then the nested properties (eg <Grid.Row>0</Grid.Row>)
			foreach (var propertyElement in xamlObjectElement.GetTags<IXamlPropertyElement>()) {
				ITypeMemberReference memberRef = propertyElement.MemberReference;
				if (memberRef != null && Equals(memberRef.Resolve().DeclaredElement, propertySetter))
					return propertyElement;
			}

			return null;
		}

		/// <summary>
		/// Gets the name node of a property setter. Handles both attribute and property element.
		/// </summary>
		/// <param name="propertySetter">The property setter</param>
		/// <returns>A <see cref="IXmlIdentifier"/> representing the name of <paramref name="propertySetter"/>, or <c>null</c> if none could be found.</returns>
		[CanBeNull]
		public static IXmlIdentifier GetSetterNameNode([CanBeNull] this IPropertySetter propertySetter) {			
			var attr = propertySetter as IPropertyAttribute;
			if (attr != null)
				return attr.NameNode;

			var element = propertySetter as IXamlPropertyElement;
			if (element != null)
				return element.HeaderNode.NameNode;

			return null;
		}

		/// <summary>
		/// Gets the value token of a <see cref="IPropertyAttribute"/>.
		/// </summary>
		/// <param name="propertyAttribute">The property attribute whose value token will be returned.</param>
		/// <returns>The value token of <paramref name="propertyAttribute"/> if available, <c>null</c> otherwise.</returns>
		[CanBeNull]
		public static IXmlToken GetValueToken([CanBeNull] this IPropertyAttribute propertyAttribute) {
			if (propertyAttribute != null) {
				IPropertyAttributeValue value = propertyAttribute.Value;
				if (value != null)
					return value.GetTextToken();
			}
			return null;
		}

		/// <summary>
		/// Gets the value token of a <see cref="IXamlPropertyElement"/>.
		/// </summary>
		/// <param name="propertyElement">The property element whose value token will be returned.</param>
		/// <returns>The value token of <paramref name="propertyElement"/> if available, <c>null</c> otherwise.</returns>
		[CanBeNull]
		public static IXmlToken GetValueToken([CanBeNull] this IXamlPropertyElement propertyElement) {
			if (propertyElement != null) {
				TreeTextRange range;
				return propertyElement.GetSingleInnerToken(out range);
			}
			return null;
		}

		/// <summary>
		/// Gets the value token of a <see cref="IPropertySetter"/>. Handles both attribute and property element.
		/// </summary>
		/// <param name="propertySetter">The property setter whose value token will be returned.</param>
		/// <returns>The value token of <paramref name="propertySetter"/> if available, <c>null</c> otherwise.</returns>
		[CanBeNull]
		public static IXmlToken GetValueToken([CanBeNull] this IPropertySetter propertySetter) {
			var attr = propertySetter as IPropertyAttribute;
			if (attr != null)
				return attr.GetValueToken();

			var element = propertySetter as IXamlPropertyElement;
			if (element != null)
				return element.GetValueToken();

			return null;
		}

		/// <summary>
		/// Gets the name of the size for a given grid definition.
		/// </summary>
		/// <param name="definitionType">The type of grid definition.</param>
		/// <returns>Height or Width, depending if <paramref name="definitionType"/> represents a row or column.</returns>
		[NotNull]
		public static string GetSizeString(this GridDefinitionType definitionType) {
			switch (definitionType) {
				case GridDefinitionType.Row:
					return "Height";
				case GridDefinitionType.Column:
					return "Width";
				default:
					throw new ArgumentOutOfRangeException("definitionType");
			}
		}

	}

}